import { Injectable, UnauthorizedException, Logger } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../../users/users.service';
import { DevicesService } from '../../devices/devices.service';
import { RedisService } from '../redis/redis.service';
import { UserRole, DeviceStatus } from '../../common';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
import { JwtTokenPayload } from '../interfaces/jwt-token-payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  private readonly logger = new Logger(JwtStrategy.name);

  constructor(
    private readonly configService: ConfigService,
    private readonly usersService: UsersService,
    private readonly devicesService: DevicesService,
    private readonly redisService: RedisService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  /**
   * Validate JWT payload and return user object
   * @param payload JWT payload
   * @returns User object
   */
  async validate(payload: JwtTokenPayload): Promise<JwtPayload> {
    const { sub, username, role, deviceSerialNumber, sessionId } = payload;

    // Find user by ID
    const user = await this.usersService.findById(sub);
    if (!user) {
      this.logger.warn(`JWT validation failed: User ${sub} not found`);
      throw new UnauthorizedException('User not found3');
    }

    // Check if session exists
    const session = await this.redisService.getSession(sessionId);
    if (!session) {
      this.logger.warn(`JWT validation failed: Session ${sessionId} not found`);
      throw new UnauthorizedException('Session not found');
    }

    // For admin users, we don't need to check device
    if (role === UserRole.ADMIN) {
      this.logger.debug(`Admin user ${username} authenticated`);
      return {
        userId: user.id,
        username: user.username,
        role: user.role,
        sessionId,
      };
    }

    // For regular users, we need to check device
    if (!deviceSerialNumber) {
      this.logger.warn(`JWT validation failed: Missing device serial number for user ${username}`);
      throw new UnauthorizedException('Invalid token payload');
    }

    // Check if device exists and is assigned to this user
    const device = await this.devicesService.findBySerialNumber(deviceSerialNumber);
    if (!device) {
      this.logger.warn(`JWT validation failed: Device ${deviceSerialNumber} not found`);
      throw new UnauthorizedException('Device not found');
    }

    if (device.userId !== user.id) {
      this.logger.warn(
        `JWT validation failed: Device ${deviceSerialNumber} not assigned to user ${username}`,
      );
      throw new UnauthorizedException('Device not assigned to this user');
    }

    if (device.status !== DeviceStatus.ACTIVE) {
      this.logger.warn(`JWT validation failed: Device ${deviceSerialNumber} is not active`);
      throw new UnauthorizedException('Device is not active');
    }

    this.logger.debug(`User ${username} authenticated with device ${deviceSerialNumber}`);

    // Return user object with additional info
    return {
      userId: user.id,
      username: user.username,
      role: user.role,
      deviceSerialNumber,
      sessionId,
    };
  }
}
